package pso.gui;

import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Arrays;
import java.util.List;

import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JProgressBar;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;

import pso.AlgorithmsDataList;
import pso.PSOAlgorithm;
import pso.gui.visualization.ConvergenceChartSeries;
import pso.gui.visualization.ConvergenceChartSeries.StatisticsType;
import pso.gui.visualization.SwarmVisualizationPanel;
import pso.mcpso.MCPSOAlgorithm;
import pso.mcpso.PSOEpochRunner;
import evolution.problem.FitnessFunction;
import evolution.problem.OptimizationProblem;

public class ProgressDialogController {

	private ProgressDialog progressDialog;

	public void run(final MCPSOAlgorithm mcpso) {
		progressDialog = new ProgressDialog();

		OptimizationProblem problem = mcpso.getOptimizationProblem();
		SwarmVisualizationPanel visualizationpanel = SwarmVisualizationPanel
				.buildPanel(problem);
		mcpso.addPropertyChangeListener("iteration", visualizationpanel);

		progressDialog.setSwarmVisualizationPanel(visualizationpanel);

		final JProgressBar progressBar = progressDialog.getProgressBar();
		progressBar.setStringPainted(true);
		progressBar.setString("Algorytm w toku...");

		final JTextArea textArea = progressDialog.getSummaryTextArea();
		textArea.setWrapStyleWord(true);
		textArea.setLineWrap(true);

		final AlgorithmsDataList algorithmsDataList = AlgorithmsDataList
				.getInstance();
		mcpso.addPropertyChangeListener("iteration",
				new PropertyChangeListener() {

					@Override
					public void propertyChange(PropertyChangeEvent evt) {
						processIteration(mcpso, progressBar, textArea,
								algorithmsDataList);
					}
				});

		XYSeriesCollection xySeriesCollection = new XYSeriesCollection();
		xySeriesCollection.addSeries(new ConvergenceChartSeries(mcpso,
				"Stado nadrzędne (najlepszy)", StatisticsType.BEST));
		xySeriesCollection.addSeries(new ConvergenceChartSeries(mcpso,
				"Stado nadrzędne (mediana)", StatisticsType.MEDIAN));
		List<PSOEpochRunner> slaves = mcpso.getSlaves();

		int index = 0;
		for (PSOEpochRunner psoEpochRunner : slaves) {
			PSOAlgorithm algorithm = psoEpochRunner.getPSOAlgorithm();

			xySeriesCollection.addSeries(new ConvergenceChartSeries(algorithm,
					algorithmsDataList.getDisplayData(algorithm).toString()
							+ " " + index++, StatisticsType.BEST));
		}
		JFreeChart chart = ChartFactory.createXYLineChart("Wykres zbieżności",
				"Iteracja", "Wartość funkcji", xySeriesCollection,
				PlotOrientation.VERTICAL, true, true, false);
		XYPlot xyPlot = (XYPlot) chart.getPlot();
		NumberAxis domain = (NumberAxis) xyPlot.getDomainAxis();
		int stopParameter = mcpso.getStopCondition().getParameter();
		domain.setRange(0, stopParameter * 1.04999); // to create right margin

		progressBar.getModel().setMaximum(stopParameter);

		progressDialog.getConvergenceChartPanel().setChart(chart);
		progressDialog.getConvergenceChartPanel().setMouseWheelEnabled(true);

		JButton closeButton = progressDialog.getCloseButton();
		closeButton.setAction(new CloseAction());
		progressDialog.getRootPane().setDefaultButton(closeButton);

		progressDialog.initGui();

		progressDialog.pack();
		progressDialog.setLocationRelativeTo(MCPSOFrame.getInstance());
		progressDialog.setVisible(true);
	}

	private void processIteration(final MCPSOAlgorithm mcpso,
			final JProgressBar progressBar, final JTextArea textArea,
			final AlgorithmsDataList algorithmsDataList) {
		final int currentIteration = mcpso.getCurrentIteration();
		final int lastIteration = mcpso.getStopCondition().getParameter();

		SwingUtilities.invokeLater(new Runnable() {

			@Override
			public void run() {
				progressBar.getModel().setValue(currentIteration);

				if (currentIteration == lastIteration) {
					progressBar.setString("Algorytm zakończony.");

					double bestSlaveFitness = mcpso.getBestSlaveValue();
					OptimizationProblem problem = mcpso
							.getOptimizationProblem();
					FitnessFunction fitnessFunction = problem
							.getFitnessFunction();
					double bestSlaveValue = fitnessFunction
							.convertToRealValue(bestSlaveFitness);

					double bestFitness = mcpso.getSwarm()[0].getNeighborhood()
							.getBestValue();
					double bestValue = fitnessFunction
							.convertToRealValue(bestFitness);
					double[] bestPosition = mcpso.getSwarm()[0]
							.getNeighborhood().getBestPosition();

					textArea.append("Najlepsza znaleziona wartość: "
							+ (bestSlaveFitness > bestFitness ? bestSlaveValue
									: bestValue) + "\n");
					boolean optimumValueKnown = problem.isOptimumValueKnown();
					double optimumValue = problem.getOptimumValue();
					if (optimumValueKnown) {
						textArea.append("Odległość od optimum globalnego: "
								+ (bestSlaveFitness > bestFitness ? bestSlaveValue
										- optimumValue
										: bestValue - optimumValue) + "\n");
					}

					double[] globalBestPosition = getGlobalBestPosition(mcpso,
							bestSlaveFitness, bestFitness, bestPosition);
					textArea.append("Najlepsza lokalizacja: "
							+ Arrays.toString(globalBestPosition) + "\n");

					XYPlot plot = ((XYPlot) progressDialog
							.getConvergenceChartPanel().getChart().getPlot());
					XYSeriesCollection dataset = (XYSeriesCollection) plot
							.getDataset();
					List<XYSeries> series = dataset.getSeries();
					for (XYSeries xySeries : series) {
						double minY = xySeries.getMinY();
						String key = xySeries.getKey().toString();
						textArea.append(key + ": " + minY + "\n");
					}

					textArea.setCaretPosition(0);

					algorithmsDataList.clearData();
				}
			}
		});
	}

	private double[] getGlobalBestPosition(final MCPSOAlgorithm mcpso,
			double bestSlaveFitness, double bestFitness, double[] bestPosition) {
		double[] bestGlobalPosition;
		double[] bestSlavePosition = mcpso.getBestSlavePosition();
		if (bestSlavePosition == null) {
			bestGlobalPosition = bestPosition;
		} else {
			if (bestSlaveFitness > bestFitness) {
				bestGlobalPosition = bestSlavePosition;
			} else {
				bestGlobalPosition = bestPosition;
			}
		}
		return bestGlobalPosition;
	}

	private class CloseAction extends AbstractAction {

		private static final long serialVersionUID = -1493920550190952367L;

		public CloseAction() {
			super("Zamknij");
		}

		@Override
		public void actionPerformed(ActionEvent e) {
			progressDialog.dispose();
		}

	}
}
